/**
 * @file generator.c
 * @brief The foundation for the workload generator.
 */
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <math.h>
#include "cJSON.h"
#include "generator.h"
#include "random.h"
#include "resource_model.h"

/** A counter that keeps track of how many resource threads are ready to begin generating workloads. */
int threads_ready = 0;

/** A mutex to control access to the threads_ready counter. */
pthread_mutex_t threads_ready_mutex = PTHREAD_MUTEX_INITIALIZER;

/** The condition on which all resource threads wait on to begin. */
pthread_cond_t threads_start = PTHREAD_COND_INITIALIZER;

void setup_generator() {
  printf("Setting up the Workload Generator.\n");
  setup_random_environment();
}

void rendezvous(pthread_cond_t *start, pthread_mutex_t *mutex, int *counter, int thr_count, char *message) {
  pthread_mutex_lock(mutex);
  if(++*counter == thr_count) {
    printf("%s\n",message);
    pthread_cond_broadcast(start);
  }
  else {
    pthread_cond_wait(start,mutex);
  }
  pthread_mutex_unlock(mutex);
}

cJSON * parse_config() {
  FILE *f= fopen("../conf/config.json","rb");
  fseek(f,0,SEEK_END);
  long len=ftell(f);
  fseek(f,0,SEEK_SET);
  char *data=malloc(len+1);
  fread(data,1,len,f);
  fclose(f);
  cJSON *config = cJSON_Parse(data);
  if (!config || config->type != 5) {
    perror("Config file is either not valid JSON or not an array.\n"); exit(2);
  }
  free(data);
  return config;
}

int workload_generator(cJSON *config, char *resource_label, resource_model *(*init)(cJSON *), pthread_t *thread)
{
  cJSON *resource = NULL;
  cJSON *c = config->child;
  for(c; c; c = c->next) {
    cJSON *c_name = cJSON_GetObjectItem(c, "resource");
    if(c_name && (c_name->type == 4)
       && (strcmp(resource_label, c_name->valuestring) == 0) )
      {
        resource = c;
      }
  }
  if(!resource)
    {
      printf("No signature found for %s resource.\n", resource_label);
      return -1;
    }
  void *model = (void *) init(resource);
  if(!model)
    {
      char **buf;
      asprintf(buf, "%s failed to initialize.\n", resource_label);
      perror(*buf);
      return -1;
    }
  pthread_create(thread, NULL, thr_resource, model);
}

pthread_t * spawn_job(resource_model *model)
{
  mode *m = select_mode(model);
  pthread_t *thread = malloc(sizeof(pthread_t));
  pthread_create(thread, NULL,m->function, model->state);
  pthread_detach(*thread);
  return thread;
}

void * thr_resource(void *config)
{
  setup_schedule();
  struct timespec current_time = {0};
  resource_model *model = (resource_model *) config;

  rendezvous(&threads_start, &threads_ready_mutex, &threads_ready, CNT_RESOURCES, "Starting Workload Generator.");
  //Start the generator
  model->birth(model);
  
  while(has_event()) {
    event *e = next_event();
    sleep_until(e->timestamp);
    e->job(e->args);
    free(e->timestamp);
    free(e);
  }
}

struct timespec * ms_from_now(double ms){
  struct timespec *result = malloc(sizeof(struct timespec));
  struct timespec now = {0};
  struct timespec to_add = {0};
  clock_gettime(CLOCK_THREAD_CPUTIME_ID,&now);
  to_add.tv_sec = (unsigned long) floor(ms / 1000);
  to_add.tv_nsec = ((unsigned long) floor( (floatmod(ms,1000) * 1E6) ) ) % (unsigned long) 1E9;
  *result = add(now,to_add);
  return result;
}

void sleep_until(struct timespec *timestamp)
{
  struct timespec current_time = {0};
  clock_gettime(CLOCK_THREAD_CPUTIME_ID,&current_time);
  struct timespec time_to_sleep = diff(&current_time, timestamp);
  nanosleep(&time_to_sleep, NULL);
}

struct timespec add(struct timespec x, struct timespec y) {
  struct timespec result = {0};
  unsigned long nsec_add = x.tv_nsec + y.tv_nsec;
  result.tv_sec = x.tv_sec + y.tv_sec + ( floor( nsec_add / 1E9 ) );
  result.tv_nsec = nsec_add % (unsigned long) 1E9;
  return result;
}

struct timespec diff(struct timespec *now, struct timespec *future)
{
  struct timespec to_sleep = {0};
  if ((future->tv_nsec-now->tv_nsec)<0) {
    to_sleep.tv_sec = future->tv_sec-now->tv_sec-1;
    to_sleep.tv_nsec = 1000000000+future->tv_nsec-now->tv_nsec;
  } else {
    to_sleep.tv_sec = future->tv_sec-now->tv_sec;
    to_sleep.tv_nsec = future->tv_nsec-now->tv_nsec;
  }
  return to_sleep;
}

double floatmod(double val, unsigned long mod) {
  double to_subtract = floor(val/mod);
  return val - (to_subtract * mod);
}
